<template>
  <div class="offline-speech-container">
    <el-card class="speech-card">
      <template #header>
        <div class="card-header">
          <h2>离线语音转文字</h2>
          <el-tag :type="isRecording ? 'danger' : 'success'">
            {{ isRecording ? '正在录音' : '准备就绪' }}
          </el-tag>
        </div>
      </template>
      
      <div class="speech-content">
        <!-- 录音控制区域 -->
        <div class="control-section">
          <el-button 
            :type="isRecording ? 'danger' : 'primary'"
            :icon="isRecording ? 'VideoPause' : 'Microphone'"
            size="large"
            @click="toggleRecording"
            :loading="isProcessing"
            :disabled="!isSupported"
          >
            {{ isRecording ? '停止录音' : '开始录音' }}
          </el-button>
          
          <el-button 
            type="warning"
            icon="Delete"
            size="large"
            @click="clearText"
            :disabled="!recognizedText"
          >
            清空文本
          </el-button>
          
          <el-button 
            type="info"
            icon="Download"
            size="large"
            @click="downloadAudio"
            :disabled="!audioBlob"
          >
            下载音频
          </el-button>
        </div>
        
        <!-- 音频可视化 -->
        <div class="audio-visualization" v-if="isRecording">
          <canvas ref="visualizationCanvas" width="400" height="100"></canvas>
          <div class="audio-level">
            <span>音量: {{ audioLevel }}%</span>
          </div>
        </div>
        
        <!-- 录音状态显示 -->
        <div class="status-section">
          <div class="recording-info" v-if="isRecording">
            <el-icon class="recording-icon"><Microphone /></el-icon>
            <span>录音时长: {{ formatTime(recordingTime) }}</span>
            <span>文件大小: {{ formatFileSize(audioSize) }}</span>
          </div>
          
          <div class="error-message" v-if="errorMessage">
            <el-alert
              :title="errorMessage"
              type="error"
              :closable="false"
              show-icon
            />
          </div>
        </div>
        
        <!-- 识别结果 -->
        <div class="result-section">
          <h3>识别结果：</h3>
          <el-input
            v-model="recognizedText"
            type="textarea"
            :rows="6"
            placeholder="录音结果将显示在这里..."
            readonly
            class="result-textarea"
          />
          
          <div class="result-actions" v-if="recognizedText">
            <el-button 
              type="primary" 
              icon="CopyDocument"
              @click="copyText"
            >
              复制文本
            </el-button>
            <el-button 
              type="success" 
              icon="Download"
              @click="downloadText"
            >
              下载文本
            </el-button>
          </div>
        </div>
        
        <!-- 使用说明 -->
        <div class="help-section">
          <el-collapse>
            <el-collapse-item title="使用说明" name="help">
              <ul class="help-list">
                <li>点击"开始录音"按钮开始录制音频</li>
                <li>请确保浏览器已授权麦克风权限</li>
                <li>录音过程中可以看到音频可视化</li>
                <li>点击"停止录音"结束录制</li>
                <li>录音文件会自动保存，可以下载</li>
                <li>此功能完全离线，不依赖网络连接</li>
              </ul>
            </el-collapse-item>
          </el-collapse>
        </div>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import RecordRTC from 'recordrtc'

// 响应式数据
const isRecording = ref(false)
const isProcessing = ref(false)
const recognizedText = ref('')
const errorMessage = ref('')
const isSupported = ref(false)
const audioBlob = ref<Blob | null>(null)
const recordingTime = ref(0)
const audioSize = ref(0)
const audioLevel = ref(0)
const visualizationCanvas = ref<HTMLCanvasElement | null>(null)

// 录音相关
let recorder: RecordRTC | null = null
let mediaStream: MediaStream | null = null
let recordingTimer: number | null = null
let animationFrame: number | null = null

// 检查浏览器支持
const checkBrowserSupport = () => {
  const hasGetUserMedia = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
  const hasRecordRTC = typeof RecordRTC !== 'undefined'
  
  isSupported.value = hasGetUserMedia && hasRecordRTC
  
  if (!isSupported.value) {
    errorMessage.value = '当前浏览器不支持音频录制功能，请使用Chrome、Firefox或Safari浏览器'
  }
  
  return isSupported.value
}

// 初始化录音器
const initRecorder = async () => {
  try {
    mediaStream = await navigator.mediaDevices.getUserMedia({
      audio: {
        echoCancellation: true,
        noiseSuppression: true,
        autoGainControl: true
      }
    })
    
    recorder = new RecordRTC(mediaStream, {
      type: 'audio',
      mimeType: 'audio/wav',
      sampleRate: 44100,
      numberOfAudioChannels: 1,
      timeSlice: 100,
      ondataavailable: (blob) => {
        audioSize.value = blob.size
      }
    })
    
    return true
  } catch (error: any) {
    console.error('初始化录音器失败:', error)
    errorMessage.value = `初始化录音器失败: ${error.message}`
    return false
  }
}

// 开始录音
const startRecording = async () => {
  if (!recorder) {
    const success = await initRecorder()
    if (!success) return
  }
  
  try {
    recorder.startRecording()
    isRecording.value = true
    recordingTime.value = 0
    audioSize.value = 0
    
    // 开始计时
    recordingTimer = window.setInterval(() => {
      recordingTime.value++
    }, 1000)
    
    // 开始音频可视化
    startAudioVisualization()
    
    ElMessage.success('开始录音')
  } catch (error: any) {
    console.error('开始录音失败:', error)
    errorMessage.value = `开始录音失败: ${error.message}`
    ElMessage.error('开始录音失败')
  }
}

// 停止录音
const stopRecording = async () => {
  if (!recorder) return
  
  try {
    recorder.stopRecording(() => {
      const blob: Blob = recorder!.getBlob()
      audioBlob.value = blob
      audioSize.value = blob.size
      
      // 这里可以添加音频处理逻辑
      // 由于是离线版本，我们主要提供录音功能
      recognizedText.value = `录音完成！\n文件大小: ${formatFileSize(blob.size)}\n录音时长: ${formatTime(recordingTime.value)}\n\n注意：这是离线录音功能，如需语音转文字，请使用在线版本或手动转录。`
      
      ElMessage.success('录音完成')
    })
    
    isRecording.value = false
    
    // 停止计时
    if (recordingTimer) {
      clearInterval(recordingTimer)
      recordingTimer = null
    }
    
    // 停止音频可视化
    stopAudioVisualization()
    
  } catch (error: any) {
    console.error('停止录音失败:', error)
    errorMessage.value = `停止录音失败: ${error.message}`
    ElMessage.error('停止录音失败')
  }
}

// 切换录音状态
const toggleRecording = async () => {
  if (isProcessing.value) return
  
  isProcessing.value = true
  errorMessage.value = ''
  
  try {
    if (isRecording.value) {
      await stopRecording()
    } else {
      await startRecording()
    }
  } finally {
    isProcessing.value = false
  }
}

// 音频可视化
const startAudioVisualization = () => {
  if (!mediaStream || !visualizationCanvas.value) return
  
  const canvas = visualizationCanvas.value
  const ctx = canvas.getContext('2d')
  if (!ctx) return
  
  const audioContext = new AudioContext()
  const analyser = audioContext.createAnalyser()
  const source = audioContext.createMediaStreamSource(mediaStream)
  
  source.connect(analyser)
  analyser.fftSize = 256
  
  const bufferLength = analyser.frequencyBinCount
  const dataArray = new Uint8Array(bufferLength)
  
  const draw = () => {
    if (!isRecording.value) return
    
    animationFrame = requestAnimationFrame(draw)
    
    analyser.getByteFrequencyData(dataArray)
    
    // 计算平均音量
    const average = dataArray.reduce((a, b) => a + b) / bufferLength
    audioLevel.value = Math.round((average / 255) * 100)
    
    // 绘制音频波形
    ctx.fillStyle = '#f0f0f0'
    ctx.fillRect(0, 0, canvas.width, canvas.height)
    
    ctx.fillStyle = '#409EFF'
    const barWidth = canvas.width / bufferLength
    
    for (let i = 0; i < bufferLength; i++) {
      const barHeight = (dataArray[i] / 255) * canvas.height
      ctx.fillRect(i * barWidth, canvas.height - barHeight, barWidth - 1, barHeight)
    }
  }
  
  draw()
}

// 停止音频可视化
const stopAudioVisualization = () => {
  if (animationFrame) {
    cancelAnimationFrame(animationFrame)
    animationFrame = null
  }
  audioLevel.value = 0
}

// 清空文本
const clearText = () => {
  recognizedText.value = ''
  audioBlob.value = null
  audioSize.value = 0
  recordingTime.value = 0
  ElMessage.info('已清空')
}

// 复制文本
const copyText = async () => {
  try {
    await navigator.clipboard.writeText(recognizedText.value)
    ElMessage.success('文本已复制到剪贴板')
  } catch (error) {
    ElMessage.error('复制失败')
  }
}

// 下载文本
const downloadText = () => {
  const blob = new Blob([recognizedText.value], { type: 'text/plain' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `录音文本_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
  ElMessage.success('文本已下载')
}

// 下载音频
const downloadAudio = () => {
  if (!audioBlob.value) return
  
  const url = URL.createObjectURL(audioBlob.value)
  const a = document.createElement('a')
  a.href = url
  a.download = `录音_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.wav`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
  ElMessage.success('音频已下载')
}

// 格式化时间
const formatTime = (seconds: number) => {
  const mins = Math.floor(seconds / 60)
  const secs = seconds % 60
  return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}

// 格式化文件大小
const formatFileSize = (bytes: number) => {
  if (bytes === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

// 组件挂载
onMounted(() => {
  checkBrowserSupport()
})

// 组件卸载时清理
onUnmounted(() => {
  if (recordingTimer) {
    clearInterval(recordingTimer)
  }
  if (animationFrame) {
    cancelAnimationFrame(animationFrame)
  }
  if (recorder) {
    recorder.destroy()
  }
  if (mediaStream) {
    mediaStream.getTracks().forEach(track => track.stop())
  }
})
</script>

<style scoped>
.offline-speech-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.speech-card {
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.card-header h2 {
  margin: 0;
  color: #303133;
}

.speech-content {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.control-section {
  display: flex;
  gap: 15px;
  justify-content: center;
  flex-wrap: wrap;
}

.audio-visualization {
  text-align: center;
  padding: 20px;
  background: #f8f9fa;
  border-radius: 8px;
}

.audio-visualization canvas {
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  background: white;
}

.audio-level {
  margin-top: 10px;
  font-size: 14px;
  color: #606266;
}

.status-section {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.recording-info {
  display: flex;
  align-items: center;
  gap: 15px;
  padding: 15px;
  background: #f0f9ff;
  border: 1px solid #b3d8ff;
  border-radius: 8px;
  color: #409EFF;
}

.recording-icon {
  animation: pulse 1.5s infinite;
}

@keyframes pulse {
  0% { opacity: 1; }
  50% { opacity: 0.5; }
  100% { opacity: 1; }
}

.result-section {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.result-section h3 {
  margin: 0;
  color: #303133;
}

.result-textarea {
  font-family: 'Courier New', monospace;
}

.result-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
}

.help-section {
  margin-top: 20px;
}

.help-list {
  margin: 0;
  padding-left: 20px;
}

.help-list li {
  margin: 8px 0;
  color: #606266;
  line-height: 1.6;
}

.error-message {
  margin-top: 15px;
}

/* 响应式设计 */
@media (max-width: 768px) {
  .offline-speech-container {
    padding: 10px;
  }
  
  .control-section {
    flex-direction: column;
    align-items: center;
  }
  
  .recording-info {
    flex-direction: column;
    text-align: center;
    gap: 10px;
  }
  
  .result-actions {
    flex-direction: column;
  }
}
</style>
